/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-11 by Raw Material Software Ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

  ==============================================================================
*/

class D3D9Context::CachedImage  : public CachedComponentImage,
                                    public Thread
{
public:
    CachedImage (D3D9Context& context_,
                 Component& component_,
                 const D3D9PixelFormat& pixelFormat)
        : Thread ("D3D9 Rendering"),
          context (context_), component (component_),
          shadersAvailable (false),
          needsUpdate (true)		  
    {
        nativeContext = new NativeContext (component, pixelFormat);

        if (nativeContext->createdOk())
        {
            context.nativeContext = nativeContext;

           #if ! JUCE_ANDROID
            startThread (6);
           #endif
        }
        else
        {
            nativeContext = nullptr;
        }
    }

    ~CachedImage()
    {
       #if ! JUCE_ANDROID
        stopThread (10000);
       #endif
    }

    //==============================================================================
    void paint (Graphics&)
    {
        ComponentPeer* const peer = component.getPeer();

        if (peer != nullptr)
            peer->addMaskedRegion (peer->getComponent().getLocalArea (&component, component.getLocalBounds()));
    }

    void invalidateAll()
    {
        validArea.clear();
        triggerRepaint();
    }

    void invalidate (const Rectangle<int>& area)
    {
        validArea.subtract (area);
        triggerRepaint();
    }

    void releaseResources() {}

    void triggerRepaint()
    {
        needsUpdate = true;
        notify();
    }


    //==============================================================================
    bool ensureFrameBufferSize (int width, int height)
    {
        const int fbW = cachedImageFrameBuffer.getWidth();
        const int fbH = cachedImageFrameBuffer.getHeight();

        if (fbW != width || fbH != height || ! cachedImageFrameBuffer.isValid())
        {
            if (! cachedImageFrameBuffer.initialise (context, width, height))
                return false;

            validArea.clear();

        }

        return true;
    }

    void clearRegionInFrameBuffer (const RectangleList& list)
    {
		cachedImageFrameBuffer.makeCurrentRenderingTarget();
        {

			D3DVIEWPORT9 vp;
			vp.X = 0;
			vp.Y = 0;
			vp.Width =cachedImageFrameBuffer.getWidth();
			vp.Height =cachedImageFrameBuffer.getHeight();
			vp.MinZ   = 0.0f;
			vp.MaxZ   = 1.0f;

			D3D9Helpers::pd3dDevice->SetViewport( &vp );
			D3D9Helpers::clear( Colours::black );	

		}
		cachedImageFrameBuffer.releaseAsRenderingTarget();
    }

    bool renderFrame()
    {
        NativeContext::Locker locker (*nativeContext);

		//D3DVIEWPORT9 vp;
		//vp.X = 0;
		//vp.Y = 0;
		//vp.Width = component.getWidth();
		//vp.Height = component.getHeight();
		//vp.MinZ   = 0.0f;
		//vp.MaxZ   = 1.0f;
		//D3D9Helpers::pd3dDevice->SetViewport( &vp );

		//D3D9Helpers::pd3dDevice->BeginScene();

        if(context.renderer != nullptr)
        {
            context.renderer->renderD3D9();
        }
/*		
        if(context.renderComponents)
            paintComponent();
*/
		//D3D9Helpers::pd3dDevice->EndScene();

        //context.swapBuffers();
        return true;
    }

    void paintComponent()
    {
        if (needsUpdate)
        {
            MessageManagerLock mm (this);
            if (! mm.lockWasGained())
                return;

            needsUpdate = false;

            // you mustn't set your own cached image object when attaching a D3D context!
            jassert (get (component) == this);

            const Rectangle<int> bounds (component.getLocalBounds());
            if (! ensureFrameBufferSize (bounds.getWidth(), bounds.getHeight()))
                return;

            RectangleList invalid (bounds);
            invalid.subtract (validArea);
            validArea = bounds;

            if (! invalid.isEmpty())
            {
                clearRegionInFrameBuffer (invalid);

                {
                    ScopedPointer<LowLevelGraphicsContext> g (createD3D9GraphicsContext (context, cachedImageFrameBuffer));
                    //g->clipToRectangleList (invalid);
                    paintOwner (*g);  
                }

                context.makeActive();
				cachedImageFrameBuffer.releaseAsRenderingTarget();
			
            }


        }


		D3D9Helpers::pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
		D3D9Helpers::pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
		D3D9Helpers::pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

		D3D9Helpers::pd3dDevice->SetTexture(0, cachedImageFrameBuffer.getTexture());

        const Rectangle<int> cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight());
        context.copyTexture (cacheBounds, cacheBounds, context.getWidth(), context.getHeight());

		D3D9Helpers::pd3dDevice->SetTexture(0, NULL);

		D3D9Helpers::pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );


    }

    void paintOwner (LowLevelGraphicsContext& context)
    {
        Graphics g (&context);

       #if JUCE_ENABLE_REPAINT_DEBUGGING
        g.saveState();
       #endif

        JUCE_TRY
        {
            component.paintEntireComponent (g, false);
        }
        JUCE_CATCH_EXCEPTION

       #if JUCE_ENABLE_REPAINT_DEBUGGING
        // enabling this code will fill all areas that get repainted with a colour overlay, to show
        // clearly when things are being repainted.
        g.restoreState();

        static Random rng;
        g.fillAll (Colour ((uint8) rng.nextInt (255),
                           (uint8) rng.nextInt (255),
                           (uint8) rng.nextInt (255),
                           (uint8) 0x50));
       #endif
    }

    //==============================================================================
    void run()
    {
        {
            // Allow the message thread to finish setting-up the context before using it..
            MessageManagerLock mml (this);
            if (! mml.lockWasGained())
                return;
        }

        nativeContext->makeActive();
        initialiseOnThread();

       //#if JUCE_USE_D3D9_SHADERS && ! JUCE_OPENGL_ES
       // shadersAvailable = OpenGLShaderProgram::getLanguageVersion() > 0;
       //#endif

        while (! threadShouldExit())
        {
            const uint32 frameRenderStartTime = Time::getMillisecondCounter();
/*
			if (context.IsNeedResetDevice())
			{
				nativeContext->LostDevice();
				cachedImageFrameBuffer.LostDevice();
				nativeContext->ResetDevice();
			}
*/
            if (renderFrame())
                waitForNextFrame (frameRenderStartTime);
        }

        shutdownOnThread();
    }

    void initialiseOnThread()
    {
        associatedObjectNames.clear();
        associatedObjects.clear();

        nativeContext->initialiseOnRenderThread();

		//D3DVIEWPORT9 vp;
		//vp.X      = 0;
		//vp.Y      = 0;
		//vp.Width  = component.getWidth();
		//vp.Height = component.getHeight();
		//vp.MinZ   = 0.0f;
		//vp.MaxZ   = 1.0f;
		//D3D9Helpers::pd3dDevice->SetViewport( &vp );

        if (context.renderer != nullptr)
            context.renderer->newD3D9ContextCreated();
    }

    void shutdownOnThread()
    {
        if (context.renderer != nullptr)
            context.renderer->D3D9ContextClosing();

        nativeContext->shutdownOnRenderThread();

        associatedObjectNames.clear();
        associatedObjects.clear();
    }

    void waitForNextFrame (const uint32 frameRenderStartTime)
    {
        const int defaultFPS = 60;

        const int elapsed = (int) (Time::getMillisecondCounter() - frameRenderStartTime);
        wait (jmax (1, (1000 / defaultFPS) - elapsed));
    }

    //==============================================================================
    static CachedImage* get (Component& c) noexcept
    {
        return dynamic_cast<CachedImage*> (c.getCachedComponentImage());
    }

    //==============================================================================
    ScopedPointer<NativeContext> nativeContext;

    D3D9Context& context;
    Component& component;

    D3D9FrameBuffer cachedImageFrameBuffer;
    RectangleList validArea;

    StringArray associatedObjectNames;
    ReferenceCountedArray<ReferenceCountedObject> associatedObjects;

    WaitableEvent canPaintNowFlag, finishedPaintingFlag;
    bool volatile shadersAvailable;
    bool volatile needsUpdate;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImage);
};

//==============================================================================


//==============================================================================
class D3D9Context::Attachment  : public ComponentMovementWatcher
{
public:
    Attachment (D3D9Context& context_, Component& comp)
       : ComponentMovementWatcher (&comp), context (context_)
    {
        if (canBeAttached (comp))
            attach();
    }

    ~Attachment()
    {
        detach();
    }

    void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/)
    {
        Component* const comp = getComponent();

        if (isAttached (*comp) != canBeAttached (*comp))
            componentVisibilityChanged();

        context.width  = comp->getWidth();
        context.height = comp->getHeight();

        if (comp->getWidth() > 0 && comp->getHeight() > 0
             && context.nativeContext != nullptr)
        {
            context.nativeContext->updateWindowPosition (comp->getTopLevelComponent()
                                                            ->getLocalArea (comp, comp->getLocalBounds()));
        }
    }

    void componentPeerChanged()
    {
        detach();
        componentVisibilityChanged();
    }

    void componentVisibilityChanged()
    {
        Component* const comp = getComponent();

        if (canBeAttached (*comp))
        {
            if (! isAttached (*comp))
                attach();
        }
        else
        {
            detach();
        }
    }

   #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS
    void componentBeingDeleted (Component& component)
    {
        /* You must call detach() or delete your OpenGLContext to remove it
           from a component BEFORE deleting the component that it is using!
        */
        jassertfalse;

        ComponentMovementWatcher::componentBeingDeleted (component);
    }
   #endif

private:
    D3D9Context& context;

    static bool canBeAttached (const Component& comp) noexcept
    {
        return comp.getWidth() > 0 && comp.getHeight() > 0 && comp.isShowing();
    }

    static bool isAttached (const Component& comp) noexcept
    {
        return comp.getCachedComponentImage() != nullptr;
    }

    void attach()
    {
        Component* const comp = getComponent();
        comp->setCachedComponentImage (new CachedImage (context, *comp,
                                                        context.pixelFormat));
    }

    void detach()
    {
        getComponent()->setCachedComponentImage (nullptr);
        context.nativeContext = nullptr;
    }
};

//==============================================================================
D3D9Context::D3D9Context()
    : nativeContext (nullptr), renderer (nullptr), contextToShareWith (nullptr),
      width (0), height (0), renderComponents (true)
{
}

D3D9Context::~D3D9Context()
{
    detach();
}

void D3D9Context::setRenderer (D3D9Renderer* rendererToUse) noexcept
{
    // This method must not be called when the context has already been attached!
    // Call it before attaching your context, or use detach() first, before calling this!
    jassert (nativeContext == nullptr);

    renderer = rendererToUse;
}

void D3D9Context::setComponentPaintingEnabled (bool shouldPaintComponent) noexcept
{
    // This method must not be called when the context has already been attached!
    // Call it before attaching your context, or use detach() first, before calling this!
    jassert (nativeContext == nullptr);

    renderComponents = shouldPaintComponent;
}

void D3D9Context::setPixelFormat (const D3D9PixelFormat& preferredPixelFormat) noexcept
{
    // This method must not be called when the context has already been attached!
    // Call it before attaching your context, or use detach() first, before calling this!
    jassert (nativeContext == nullptr);

    pixelFormat = preferredPixelFormat;
}

void D3D9Context::setContextToShareWith (const D3D9Context* context) noexcept
{
    // This method must not be called when the context has already been attached!
    // Call it before attaching your context, or use detach() first, before calling this!
    jassert (nativeContext == nullptr);

    contextToShareWith = context;
}

void D3D9Context::attachTo (Component& component)
{
    component.repaint();

    if (getTargetComponent() != &component)
    {
        detach();

        width  = component.getWidth();
        height = component.getHeight();

        attachment = new Attachment (*this, component);
    }
}

void D3D9Context::detach()
{
    attachment = nullptr;
    nativeContext = nullptr;
    width = height = 0;
}


bool D3D9Context::isAttached() const noexcept
{
    return nativeContext != nullptr;
}

Component* D3D9Context::getTargetComponent() const noexcept
{
    return attachment != nullptr ? attachment->getComponent() : nullptr;
}

D3D9Context* D3D9Context::getCurrentContext()
{
    CachedImage* currentContext = dynamic_cast <CachedImage*> (Thread::getCurrentThread());

    return currentContext != nullptr ? &currentContext->context : nullptr;
}

bool D3D9Context::makeActive() const noexcept     { return nativeContext != nullptr && nativeContext->makeActive(); }

void D3D9Context::triggerRepaint()
{
    CachedImage* const currentContext
            = dynamic_cast <CachedImage*> (Thread::getCurrentThread());

    if (currentContext != nullptr)
    {
        currentContext->triggerRepaint();
        currentContext->component.repaint();
    }
}

//void D3D9Context::swapBuffers()
//{
//    if (nativeContext != nullptr)
//        nativeContext->swapBuffers();
//}

LPDIRECT3DSURFACE9 D3D9Context::getFrameBufferSurface() const noexcept
{
    return nativeContext->getFrameBufferSurface();
}




D3D9Context::CachedImage* D3D9Context::getCachedImage() const noexcept
{
    Component* const comp = getTargetComponent();
    return comp != nullptr ? CachedImage::get (*comp) : nullptr;
}

bool D3D9Context::areShadersAvailable() const
{
    CachedImage* const c = getCachedImage();
    return c != nullptr && c->shadersAvailable;
}


//
void D3D9Context::RestoreD3DDevice9()
{
	attachment->componentPeerChanged();
}

ReferenceCountedObject* D3D9Context::getAssociatedObject (const char* name) const
{
    jassert (name != nullptr);

    CachedImage* const c = getCachedImage();

    // This method must only be called from an D3D9 rendering callback.
    jassert (c != nullptr && nativeContext != nullptr);
    jassert (getCurrentContext() != nullptr);

    const int index = c->associatedObjectNames.indexOf (name);
    return index >= 0 ? c->associatedObjects.getUnchecked (index) : nullptr;
}

void D3D9Context::setAssociatedObject (const char* name, ReferenceCountedObject* newObject)
{
    jassert (name != nullptr);

    CachedImage* const c = getCachedImage();

    // This method must only be called from an D3D9 rendering callback.
    jassert (c != nullptr && nativeContext != nullptr);
    jassert (getCurrentContext() != nullptr);

    const int index = c->associatedObjectNames.indexOf (name);

    if (index >= 0)
    {
        c->associatedObjects.set (index, newObject);
    }
    else
    {
        c->associatedObjectNames.add (name);
        c->associatedObjects.add (newObject);
    }
}

HWND D3D9Context::GetNativeContextHandle()
{
	jassert (nativeContext != nullptr);
	return nativeContext->GetHandle();
}

void D3D9Context::copyTexture (const Rectangle<int>& targetClipArea,
                                 const Rectangle<int>& anchorPosAndTextureSize,
                                 const int contextWidth, const int contextHeight)
{
    if (contextWidth <= 0 || contextHeight <= 0)
        return;



   #if JUCE_USE_D3D9_FIXED_FUNCTION
    {
		RECT scissorRect;
		scissorRect.left = targetClipArea.getX();
		scissorRect.top = targetClipArea.getY();
		scissorRect.right = targetClipArea.getX() + targetClipArea.getWidth();
		scissorRect.bottom = targetClipArea.getY() + targetClipArea.getHeight();

		D3D9Helpers::pd3dDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, TRUE );
		D3D9Helpers::pd3dDevice->SetScissorRect( &scissorRect );

        D3D9Helpers::prepareFor2D (contextWidth, contextHeight);
 
		float left		= (float)anchorPosAndTextureSize.getX();
		float right		= (float)anchorPosAndTextureSize.getRight();
		float top		= (float)anchorPosAndTextureSize.getY();
		float bottom	= (float)anchorPosAndTextureSize.getBottom();
		

		D3D9Helpers::VertexQuadVertex vertex[4];
		vertex[0].pos[0] = left;
		vertex[0].pos[1] = top;
		vertex[0].pos[2] = 0;
		vertex[0].pos[3] = 1;
		vertex[0].color = 0xFFFFFFFF;
		vertex[0].uv[0] = 0.0f;
		vertex[0].uv[1] = 0.0f;

		vertex[1].pos[0] = right;
		vertex[1].pos[1] = top;
		vertex[1].pos[2] = 0;
		vertex[1].pos[3] = 1;
		vertex[1].color = 0xFFFFFFFF;
		vertex[1].uv[0] = 1.0f;
		vertex[1].uv[1] = 0.0f;

		vertex[2].pos[0] = left;
		vertex[2].pos[1] = bottom;
		vertex[2].pos[2] = 0;
		vertex[2].pos[3] = 1;
		vertex[2].color = 0xFFFFFFFF;
		vertex[2].uv[0] = 0;
		vertex[2].uv[1] = 1.0f;


		vertex[3].pos[0] = right;
		vertex[3].pos[1] = bottom;
		vertex[3].pos[2] = 0;
		vertex[3].pos[3] = 1;
		vertex[3].color = 0xFFFFFFFF;
		vertex[3].uv[0] = 1.0f;
		vertex[3].uv[1] = 1.0f;

		D3D9Helpers::pd3dDevice->SetFVF( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 );
		D3D9Helpers::pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, vertex, sizeof( D3D9Helpers::VertexQuadVertex ) );

		D3D9Helpers::pd3dDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, FALSE );
    }
   #endif


}
